import java.rmi.NoSuchObjectException;
import java.rmi.NotBoundException;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.rmi.server.UnicastRemoteObject;

public class Maze implements MazeInterface{

	static String SERVER_IND = "-s";
	static String CLIENT_IND = "-c";

	static final int MAZE_PORT_NO_BASE  = 29467;
	
	static final int CONNECT_PRIMARY = 1;
	static final int CONNECT_SECONDARY_FIRST_CLIENT = 2; // when primary is down, the first client to detect it
	static final int CONNECT_PRIMARY_AS_PLAYER_1 = 3; // player id 1 is assigned to server player
	static final int CONNECT_SECONDARY_OTHER_CLIENT = 4; // after detection of primary down, other clients connect
	
	static final int RMI_WAITING_TIME = 10;
	
	static int serverPort = 30416;
	static String serverHost = "localhost";
	
	public static int mapSize = 0;
	public static int treasure = 0;
	
	static GameboardInterface stubServer = null;
	static GameboardInterface stubClient = null;
	static MazeInterface stubMaze = null;
	
	static Registry gameServerRegistry=null;
	static Registry gameClientRegistry=null;
	static Registry gameMazeRegistry=null;
	
	// reference to SERVER
	static GameBoard gboard= null;
	// reference to CLIENT
	static GameClient gclient=null;
	static Maze gmaze = null;
	
	static String refGameBoard = "GameBoard";
	static String refMaze = "GameMaze";
	
	private GameStatusInfo gameStatusInfo = null;
	private PlayerMovementInfo playerMovementInfo = null;
	
	// exceptions
	static String faildToCreateBackup = "Failed to create backup server. ";
	static String backupServerNotCreated = "Backup server not yet created. gboard is null. ";
	static String connectionToServerFailed = "Connection request to server failed. ";
	static String unableToUnexport = "Unable to unexport object, before connecting to backup server. ";
	
	enum MY_ROLE {
		PRIMARY_SERVER,
		BACKUP_SERVER,
		CLIENT
	};
	
	static MY_ROLE role = null;
	
	public void SetGameStatusInfo (GameStatusInfo gameStsInfo) {
		boolean primaryChanged = (gameStatusInfo != null) && (gameStatusInfo.primaryServerPlayerID != gameStsInfo.primaryServerPlayerID); 
		gameStatusInfo = gameStsInfo;
		System.out.println("Maze message: " + gclient.getID() + " Primary server ID: " + gameStatusInfo.primaryServerPlayerID + " " + "Backup server ID " + gameStatusInfo.backUpServerPlayerID);
		
		// check if I'm backup server. Then change role
		if (gameStatusInfo.backUpServerPlayerID == gclient.getID()) {
			
			if (role == MY_ROLE.CLIENT) {
				role = MY_ROLE.BACKUP_SERVER;
				System.out.println ("Maze Message: "+ gclient.getID() +": I'm ASSIGNED AS BACKUP SERVER");
				
				// I'm selected as secondary. Create backup server
				try {
					System.out.println("Maze Message: "+ gclient.getID() + ": Creating backup server at port: "+ gameStatusInfo.backUpServerPort);
					LocateRegistry.createRegistry(gameStatusInfo.backUpServerPort);
					gboard = new GameBoard(gameStatusInfo.GetMapSize(), gameStatusInfo.GetTreasureCount(), gameStatusInfo, this);
					stubServer = (GameboardInterface) UnicastRemoteObject.exportObject(gboard, 0);
					gameServerRegistry = LocateRegistry.getRegistry(this.gameStatusInfo.backUpServerPort);
					gameServerRegistry.bind(refGameBoard, stubServer);
					gboard.setPlayersRemoteObject(gameStatusInfo.players);
				}
	
				catch (Exception ex) {
					System.out.println("Maze Exception: " + faildToCreateBackup);
					ex.printStackTrace();
				}
			}
			
		}
		
		else if (gameStatusInfo.primaryServerPlayerID == gclient.getID()) 
			role = MY_ROLE.PRIMARY_SERVER;

		//Create new stubServer
		if (primaryChanged) {
			int newServerPort = gameStatusInfo.primaryServerPort;			
			String newServerIP = gameStatusInfo.playerIPs.get(gameStatusInfo.primaryServerPlayerID);
			try {
				gameClientRegistry = LocateRegistry.getRegistry(newServerIP, newServerPort);
				stubClient = (GameboardInterface) gameClientRegistry.lookup(refGameBoard);
//				stubClient.testCallToNewPrimaryServer(gclient.getID());
			} catch (RemoteException e) {
				e.printStackTrace();
			} catch (NotBoundException e) {
				e.printStackTrace();
			}
			gclient.setGameBoardInterface(stubClient);
		}

	}
	
	public void SetPlayerMovementInfo (PlayerMovementInfo playerMovementInfo) {
		if (role == MY_ROLE.BACKUP_SERVER) {
			this.playerMovementInfo = playerMovementInfo;
			System.out.println ("Maze Message: "+ gclient.getID() + ": Player movement information received for backup server");
		
			if (gboard != null) {
				gboard.SyncPlayerMovementInfo(playerMovementInfo);
			}
			else {
				System.out.println("Maze exception: " + backupServerNotCreated);
			}
		}
	}
	
	// return gameStatusInfo only when primary server is down
	public GameStatusInfo GetGameStatusInfo () {
			return gameStatusInfo;
	}
	
	// return only if the role is primary server
	public PlayerMovementInfo GetPlayerMovementInfo () {
		if (role == MY_ROLE.PRIMARY_SERVER) 
			return playerMovementInfo;
		else
			return null;
	}
	
	
	public static void main(String[] args) {
		gmaze = new Maze();
		
		// check if start as server or client
		for (int argNo=0; argNo < args.length; argNo++){
			if (args[argNo].equals(SERVER_IND)) {
				mapSize = Integer.parseInt(args[argNo+1]);
				treasure = Integer.parseInt(args [argNo+2]);
				role = MY_ROLE.PRIMARY_SERVER;
			}
			else if (args[argNo].equals(CLIENT_IND)) {
				if (args.length >= argNo+2)
					serverHost = args[argNo+1];
				
				if (args.length >= argNo+3)
					serverPort = Integer.parseInt(args[argNo+2]);
				
				role = MY_ROLE.CLIENT;
			}
		}
		
		if (role == null){
			Usage();
			return;
		}
			
		// if Maze acts as SERVER
		if (role == MY_ROLE.PRIMARY_SERVER) {
		// check if map size and treasure count was passed
			if (mapSize == 0||treasure == 0){
				System.out.println("GameBoard -s <map size> <no of treasures>");
				return;
			}
			
			try {
				LocateRegistry.createRegistry(serverPort);
				gboard = new GameBoard(mapSize, treasure, serverPort, gmaze);
				stubServer = (GameboardInterface) UnicastRemoteObject.exportObject(gboard, 0);
				gameServerRegistry = LocateRegistry.getRegistry(serverPort);
				gameServerRegistry.bind(refGameBoard, stubServer);				
			}

			catch (Exception ex) {
				System.out.println("GameBoard Exception: " + ex.getMessage());
				ex.printStackTrace();
			}
			
			// create client instance for me
			// My client will be player id 1
			gclient = new GameClient(serverHost, serverPort);
			ConnectToNewServer(gmaze, serverHost, serverPort, CONNECT_PRIMARY_AS_PLAYER_1);
	
		}
		
		// if Maze acts as CLIENT
		if (role == MY_ROLE.CLIENT) {
			gclient = new GameClient(serverHost, serverPort);
			 {
				 System.out.println("Game Client: Connecting to host: " + serverHost + " port: " + serverPort);
				 ConnectToNewServer(gmaze, serverHost,serverPort,CONNECT_PRIMARY);
			 }
		}
	}
	
	private static void Usage() {
		System.out.println("To Create a new Game run with parameters: -s <map size> <no of treasures>");
		System.out.println("To join an existing Game run with parameters: -c [Host IP] [port]");
	}
	
	private static void ConnectToNewServer(Maze maze, String hostIP, int port, int whichServer) {
		try {
			
			UnicastRemoteObject.exportObject(gclient,0);
			gameClientRegistry = LocateRegistry.getRegistry(hostIP, port);

			stubClient = (GameboardInterface) gameClientRegistry.lookup(refGameBoard);
			gclient.setGameBoardInterface(stubClient);
			
			// pass controll to GameClient
			gclient.Connect(maze, whichServer);

		} catch (RemoteException e) {
			System.out.println("Maze exception: " + connectionToServerFailed + " Connection IP: " + hostIP + ", port: " + port);
			e.printStackTrace();
		} catch (NotBoundException e) {
			System.out.println("Maze exception: " + connectionToServerFailed + " Connection IP: " + hostIP + ", port: " + port);
			e.printStackTrace();
		}
	}
	
	// client calls to inform the primary cannot be contacted.
	public void InformPrimaryServerDown (PlayerMovementInfo playerMovmentInfo, int playerId) {
		int backupPort;
		
		// if no backup server present, backUpServerPlayerID would be -1
		// this should not happen. 
		if (gameStatusInfo.playerIPs.containsKey(gameStatusInfo.backUpServerPlayerID)) {
			String backupServerIP = gameStatusInfo.playerIPs.get(gameStatusInfo.backUpServerPlayerID);
			backupPort = gameStatusInfo.backUpServerPort;		
			try {
				UnicastRemoteObject.unexportObject(gclient,true);
			} catch (NoSuchObjectException e) {
				System.out.println("Maze exception: " + unableToUnexport);
				e.printStackTrace();
			}
			// the first client to detect primary down
			System.out.println("Maze message: "+ gclient.getID() +" Connecting to backup server. IP: " + backupServerIP + " port: " + backupPort);
			ConnectToNewServer(this, backupServerIP, backupPort, CONNECT_SECONDARY_FIRST_CLIENT);			
		}
		else{
			System.out.println("Maze message: No backup server defined");
		}
		//TODO new updated gamestatusinfo has been received. So inform other mazes
//		InformOtherMazes ();
	}

	@Override
	public void InformPrimaryDown(GameStatusInfo gameStatInfo) {
		System.out.println("Maze message: "+ gclient.getID() +": Primary down message received at player id: " + gclient.getID());
		// this is a updated version of gameStatusInfo, new primary server ip, port have been amended.
		// so connect to new primary
		gameStatusInfo = gameStatInfo;
		
		int newServerPort = gameStatInfo.primaryServerPort;
		
		String newServerIP = gameStatusInfo.playerIPs.get(gameStatInfo.primaryServerPlayerID);
		
		try {
			UnicastRemoteObject.unexportObject(gclient,true);
		} catch (NoSuchObjectException e) {
			System.out.println("Maze exception: " + unableToUnexport);
			e.printStackTrace();
		}
		System.out.println("Maze message: "+ gclient.getID() +" Connecting to new primary server. IP: " + newServerIP + " port: " + newServerPort);
		ConnectToNewServer(this, newServerIP, newServerPort, CONNECT_SECONDARY_OTHER_CLIENT);
		
	}
}
